package com.gitee.wsl.math

import com.gitee.wsl.ext.array.median
import com.gitee.wsl.ext.number.roundDecimalPlaces
//import java.math.BigInteger
import kotlin.math.abs
import kotlin.math.sqrt


fun Math.cosineSimilarity(a: Array<Float>, b: Array<Float>): Float {
    var dotProduct = 0.0f
    var normA = 0.0f
    var normB = 0.0f
    a.forEachIndexed { i, ea ->
        val eb = b[i]
        dotProduct += ea * eb
        normA += ea * ea
        normB += eb * eb
    }
    return dotProduct / (sqrt(normA) * sqrt(normB))
}


fun Math.maximalMarginalRelevance(
    queryEmbedding: Array<Float>,
    embeddingList: List<Array<Float>>,
    lambdaMult: Float = 0.5f,
    k: Int = 4
): List<Int> {
    val idxList = mutableListOf<Int>()
    while (idxList.size < k) {
        var bestScore = -Float.MAX_VALUE
        var idxToAdd = -1
        for ((i, emb) in embeddingList.withIndex()) {
            if (i in idxList) {
                continue
            }
            val firstPart = cosineSimilarity(queryEmbedding, emb)
            var secondPart = 0f
            for (j in idxList) {
                val cosSim = cosineSimilarity(emb, embeddingList[j])
                if (cosSim > secondPart) {
                    secondPart = cosSim
                }
            }
            val equationScore = lambdaMult * firstPart - (1 - lambdaMult) * secondPart
            if (equationScore > bestScore) {
                bestScore = equationScore
                idxToAdd = i
            }
        }
        idxList.add(idxToAdd)
    }
    return idxList
}

/**
 * Seeks to find the maximum value, within the range of [min] and [max] and with an accuracy
 * of [tolerance], that results in a true result from the [eval] function.
 * [eval] must be a monotonically increasing function.
 * [min] must be a value that causes [eval] to return true, and max must be greater than [min].
 * [max] may be +infinity, but otherwise [min] and [max] must be numbers.
 */
internal fun Math.maximize(
    min: Double,
    max: Double,
    tolerance: Double = 0.01,
    eval: (Double) -> Boolean
): Double {
    require(min <= max)
    require(tolerance > 0)
    require(min.isFinite())
    require(!max.isNaN())

    return maximizeUnguarded(min, max, tolerance, eval)
}

/**
 * This is the implementation of the recursive maximize function without the parameter checks
 * for performance purposes.
 */
private fun Math.maximizeUnguarded(
    min: Double,
    max: Double,
    tolerance: Double = 0.01,
    eval: (Double) -> Boolean
): Double {
    if (abs((max - min) / ((max + min) / 2)) < tolerance || (max == 0.0 && min == 0.0)) {
        return min
    }

    val test = if (max.isInfinite()) {
        if (min == 0.0) {
            1.0
        } else {
            min * 2.0
        }
    } else {
        min + (max - min) / 2.0
    }

    return if (eval(test)) {
        maximizeUnguarded(test, max, tolerance, eval)
    } else {
        maximizeUnguarded(min, test, tolerance, eval)
    }
}


/**
 * 将百分比转换为图心角角度
 * @param totalAngle    总角度(如:360度)
 * @param percentage    百分比
 * @return 圆心角度
 */
fun Math.getSliceAngle(totalAngle: Float, percentage: Float): Float {
    var angle = 0.0f
    try {
        if (percentage >= 101f || percentage < 0.0f) {
            //Log.e(TAG,"输入的百分比不合规范.须在0~100之间.");
        } else {
            angle = (totalAngle * percentage / 100f).roundDecimalPlaces(2)
        }
    } catch (ex: Exception) {
        angle = -1f
    }
    return angle
}



enum class Quartile {
    FIRST, SECOND, THIRD
}

fun DoubleArray.quartile(quartileNum: Quartile = Quartile.FIRST): Double {
    if (isEmpty()) return 0.0

    if (size == 1) return first()

    if (quartileNum == Quartile.SECOND) return median

    sort()

    if (quartileNum == Quartile.FIRST) return (sliceArray(0 until size / 2)).median

    var startIndex = size / 2
    if (size % 2 == 1) {
        startIndex++
    }

    return (sliceArray(startIndex until size)).median
}

val DoubleArray.interquartileRange: Double
    get() {
        return quartile(Quartile.THIRD) - quartile(Quartile.FIRST)
    }
/*

val DoubleArray.rank: Array<BigInteger>
    get() {
        val sortedSeq = this.sorted().toTypedArray()
        var rank = 1.toBigInteger()
        var min = sortedSeq.first()
        val ranks = mutableMapOf(min to rank)

        sortedSeq.forEach {
            if (it != min) {
                rank++
                min = it
            }

            ranks[it] = rank
        }

        return map { ranks[it]!! }.toTypedArray()
    }

val FloatArray.rank: Array<BigInteger>
    get() {
        val sortedSeq = this.sorted().toTypedArray()
        var rank = 1.toBigInteger()
        var min = sortedSeq.first()
        val ranks = mutableMapOf(min to rank)

        sortedSeq.forEach {
            if (it != min) {
                rank++
                min = it
            }

            ranks[it] = rank
        }

        return map { ranks[it]!! }.toTypedArray()
    }

val IntArray.rank: Array<BigInteger>
    get() {
        val sortedSeq = this.sorted().toTypedArray()
        var rank = 1.toBigInteger()
        var min = sortedSeq.first()
        val ranks = mutableMapOf(min to rank)

        sortedSeq.forEach {
            if (it != min) {
                rank++
                min = it
            }

            ranks[it] = rank
        }

        return map { ranks[it]!! }.toTypedArray()
    }
*/


fun DoubleArray.weightedMean(weights: DoubleArray): Double {
    if (size != weights.size) throw IllegalArgumentException("Size of sequence ${size} does not equal size of weights ${weights.size}")

    if (weights.isEmpty()) return 0.0

    var weightedVal = 0.0

    weights.forEachIndexed { index, weight -> weightedVal += (weight * this[index]) }

    return weightedVal / weights.sum()
}
